/*  -*- mode: c++; coding: utf-8; c-file-style: "stroustrup"; -*-

    Copyright 2008 Asier Aguirre <asier.aguirre@gmail.com>
    This file is part of Intromit.

    Intromit is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Intromit is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Intromit.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "time_queue.hh"
#include "refcnt.hh"
#include <unistd.h>
#include <string.h>
#include <sys/time.h>

#define QUEUE_SIZE     1024  // must be power of 2
#define QUEUE_MASK     (QUEUE_SIZE-1)
#define INC_Q_OBJ      16
#define USEC_INTERVAL  10000

CLASS_INIT(time_queue); // TIME_QUEUE CLASS

time_queue::time_queue()
{
    CLASS_CTOR(time_queue);
    ctor();

    q=(queue *)malloc(QUEUE_SIZE*sizeof(queue));
}

time_queue::~time_queue()
{
    CLASS_DTOR(time_queue);
    dtor();

    free(q);
}

void time_queue::ctor()
{
    util::ctor_vector(obj, n_obj, max_obj);
    q_ini=q_end=0;
}

void time_queue::dtor()
{
    q_ini=q_end=0; // empty queue
    for(int i=0; i<n_obj; i++)
	if(obj[i].callback && obj[i].obj) obj[i].obj->decref();
    util::dtor_vector(obj, n_obj, max_obj);
}

bool time_queue::add(time_queue_callback callback, reference_counter *obj_, void *data)
{
    // check
    if(!callback) return false;

    // test if already added
    for(int i=0; i<n_obj; i++)
	if(obj[i].callback==callback && obj[i].obj==obj_ && obj[i].data==data) return false; // aready added

    // check structure holes
    int i;
    for(i=0; i<n_obj; i++) if(!obj[i].callback) break;
    if(i==n_obj) i=util::inc_vector(obj, n_obj, max_obj, INC_Q_OBJ);
    obj[i].callback=callback;
    obj[i].obj=obj_;
    obj[i].data=data;

    // take a reference of the object
    if(obj_) obj_->incref();

    // add obj to event queue for the first time
    add(i, time_now());
    return true;
}

bool time_queue::remove(time_queue_callback callback, reference_counter *obj_, void *data)
{
    bool dev=false;
    for(int i=0; i<n_obj; i++)
    {
	if(!obj[i].callback) continue; // hole

	if(callback && obj[i].callback!=callback) continue;
	if(obj_ && obj[i].obj!=obj_) continue;
	if(data && obj[i].data!=data) continue;

	// remove this object
	if(obj[i].obj)
	{
	    obj[i].obj->decref();
	    obj[i].obj=NULL;
	}
	obj[i].callback=NULL;

	// remove events of i
	if(q_ini!=q_end)
	{
	    int j=q_ini;
	    do
	    {
		j=(j-1)&QUEUE_MASK;
		if(q[j].i_queue_obj==i) del(j);
	    }
	    while(j!=q_end);
	}
	dev=true;
    }
    return dev;
}

bool time_queue::remove_all()
{
    return remove();
}

bool time_queue::refresh(bool control_idle_time)
{
    if(q_ini==q_end)
    {
	// empty queue
	if(control_idle_time) usleep(USEC_INTERVAL);
    }
    else
    {
	// events in queue
	unsigned now=time_now(), time_next_event=q[q_end].time;

	if(time_compare(now, time_next_event))
	{
	    // attend all events expired
	    do
	    {
		// time reached for this event, execute
		int i_queue_obj=q[q_end].i_queue_obj;
		queue_object &o=obj[i_queue_obj];
		int usec_refresh=o.callback(o.obj, o.data);
		extract_first();
		if(usec_refresh>=0) add(i_queue_obj, now+usec_refresh);
		else remove(o.callback, o.obj, o.data);

		if(!usec_refresh) break;
		time_next_event=q[q_end].time;
	    }
	    while(q_ini!=q_end && time_compare(now, time_next_event));
	}
	else
	{
	    // have time until next event
	    if(control_idle_time)
	    {
		int diff=time_next_event-now;
		if(diff>USEC_INTERVAL)
		    usleep(USEC_INTERVAL);
		else
		    usleep(diff);
	    }
	}
    }
    return true;
}

void time_queue::extract_first()
{
    if(q_ini!=q_end) q_end=(q_end+1)&QUEUE_MASK;
}

void time_queue::add(int i_queue_obj, unsigned time)
{
    if(!((q_ini+1-q_end) & QUEUE_MASK)) ERROR_R("event queue full!");

    int i=q_end;
    while(i!=q_ini)
    {
	if(time_compare(q[i].time, time)) break;
	i=(i+1)&QUEUE_MASK;
    }

    int j=q_ini;
    int jr=(q_ini-1)&QUEUE_MASK;
    q_ini=(q_ini+1)&QUEUE_MASK;
    while(i!=j)
    {
	memcpy(&q[j], &q[jr], sizeof(queue));
	j=jr;
	jr=(jr-1)&QUEUE_MASK;
    }

    q[i].i_queue_obj=i_queue_obj;
    q[i].time=time;
}

void time_queue::del(int i)
{
    while((i+1-q_ini)&QUEUE_MASK)
    {
	memcpy(&q[i], &q[(i+1)&QUEUE_MASK], sizeof(queue));
	i=(i+1)&QUEUE_MASK;
    }
    q_ini=(q_ini-1)&QUEUE_MASK;
}

unsigned time_queue::time_now()
{
    struct timeval tv;
    gettimeofday(&tv, NULL);
    return tv.tv_sec*1000000+tv.tv_usec;
}

bool time_queue::time_compare(unsigned time1, unsigned time2)
{
    return ((int)(time1-time2)>0);
}
